# -*- coding: utf-8 -*-
#Get DNS answer
#详情见RFC 1035
import os, sys
import socket
import struct
import random
from domaintobyte import domaintobyte, bytetodomainDHOST = '208.67.222.222' #DNS 服务器的地址
DPORT = 53              #默认端口是53LHOST = ''
LPORT = 10001           #绑定到的本地端口TIMEOUT = 3.0           #超时设置为3秒##数据包整体的格式
##    +---------------------+
##    |        Header       |
##    +---------------------+
##    |       Question      | the question for the name server
##    +---------------------+
##    |        Answer       | RRs answering the question
##    +---------------------+
##    |      Authority      | RRs pointing toward an authority
##    +---------------------+
##    |      Additional     | RRs holding additional information
##    +---------------------+def QueryDNS(domain):
    TID = random.randint(-32768, 32767)
    Flags = 0x0100
    Questions = 0x0001 
    AnswerRRs = 0x0000
    AuthorityRRs = 0x0000
    AdditionalRRs = 0x0000    TIDCHARS = struct.pack('!h', TID)    domainbyte = domaintobyte(domain)
    #TYPE            value and meaning
    #A               1 a host address
    #NS              2 an authoritative name server
    #MD              3 a mail destination (Obsolete - use MX)
    #MF              4 a mail forwarder (Obsolete - use MX)
    #CNAME           5 the canonical name for an alias
    SEARCHTYPE = 0x0001 
    #Class 一般为 1 指 Internet
    SEARCHCLASS = 0x0001    #构造请求报文
    Bufhead = struct.pack('!hhhhhh', TID, Flags, Questions, AnswerRRs, AuthorityRRs, AdditionalRRs)
    Buftail = struct.pack('!hh', SEARCHTYPE, SEARCHCLASS)
    Buf = Bufhead + domainbyte + Buftail    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(TIMEOUT) #设置超时时间
    s.bind((LHOST,LPORT))
    
    s.sendto(Buf,(DHOST, DPORT))
    print 'Send [OK]'
    
    try:
        data, addr = s.recvfrom(1024)
    except socket.timeout:
        s.close()
        print u'响应超时'
        return        
    s.close()
    print 'From', addr
    #print 'Receved', repr(data)
    if data[0:2] == TIDCHARS:
        #Header 部分
        #[0:2]数据包最开始两个字节是TID
        print 'TID [0k]'
        #[2:4]接着是Flags 两个字节
        #Flags 的最后4位是错误代码,如果是0000则表示没有错误,否则就有错误
        flags = struct.unpack('!h', data[2:4])[0]
        errormsg = flags & 0x000F
        if errormsg != 0:
            print "Error, maybe no such domain"
            return
        #[4:6]Questions 数 两个字节
        #[6:8]Answer RRs数    两个字节
        #[8:10]Authority RRs数   两个字节
        #[10:12]Additional RRs数   两个字节
        answerRRs = struct.unpack('!h', data[6:8])[0]        bitflags = 12; #跳过上面12个字节
        
        #Question 部分
        #Name: 域名－不定长度，以'\0'结尾
        #Type: 查询类型 两个字节
        #Class:一般为1(Internet) 两个字节
        
        while data[bitflags] != '\x00':
            bitflags += 1
        bitflags += 1 #域名结束        bitflags += 2 #跳过Type
        bitflags += 2 #跳过Class
               print 'Answers', answerRRs
        i = 0#Answer 部分一条记录的格式
#
#                                    1 1 1 1 1 1
#      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    |                                               |
#    /                                               /
#    /                      NAME                     /
#    |                                               |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    |                      TYPE                     |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    |                     CLASS                     |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    |                      TTL                      |
#    |                                               |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    |                   RDLENGTH                    |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
#    /                     RDATA                     /
#    /                                               /
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+        while i < answerRRs:
            #Name 两个字节 指针-指向域名的字符
            bitflags += 2
            if data[bitflags:bitflags+2] == '\x00\x05':#CNAME
                #Type 两个字节
                #Class两个字节
                #TTL 四个字节
                bitflags += 8
                rdatalength = struct.unpack('!h', data[bitflags:bitflags+2])[0]
                #Data length 两个字节
                bitflags += 2
                #得到rdata中的全部域名
                fullRecord = GetFullName(bitflags, data)
                bitflags += rdatalength
                print 'CNAME:', bytetodomain(fullRecord)
            elif data[bitflags:bitflags+2] == '\x00\x01':#A(Host Address)
                #Type 两个字节
                #Class两个字节
                #TTL 四个字节
                bitflags += 8
                rdatalength = struct.unpack('!h', data[bitflags:bitflags+2])[0]
                #Data length 两个字节
                bitflags += 2
                #A记录的RDatalength = 4
                iptuple = struct.unpack('!BBBB', data[bitflags:bitflags+4])
                ipstr = '%d.%d.%d.%d' % iptuple
                bitflags += rdatalength
                print 'IP:', ipstr            i += 1 #CNAME记录中域名并不是全部存储在rdata中，和Query或者其他包含域名部分有重复的部分是用一个指针，指向数据包的一个地址,这样就节省了存储空间
#offset是相对数据包头部的偏移量
#两个字节 16bit如下图所示
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#    | 1 1|                OFFSET                   |
#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#比如
#Question部分中的查询的是www.sina.com.cn
#CNAME记录中的域名是jupiter.sina.com.cn，只用在rdata中存储"jupiter"，接下来的部分用一个指针指向Question部分中的"sina.com.cn"
#CNAME中的rdata
#    +--+--+--+--+--+--+--+--+--+--+
#    |07| j| u| p| i| t| e| r|c0|10|
#    +--+--+--+--+--+--+--+--+--+--+
#最后两个字节就是一个指针，指向0x10这个地址
def GetFullName(offset, Buf):
    fullRecord = ''
    oneChar = struct.unpack('!B', Buf[offset:offset+1])[0]
    #print oneChar
    if oneChar & 0xc0 == 0xc0 : #指针
        jump = struct.unpack('!h', Buf[offset:offset+2])[0]
        jump = jump & 0x3FFF    #指针指向的地址
        fullRecord += GetFullName(jump, Buf)
    elif oneChar == 0 :         #域名以\0结束
        return '\x00'
    else :                      #域名部分
        fullRecord += Buf[offset:offset+oneChar+1]
        fullRecord += GetFullName(offset+oneChar+1, Buf)
        
    return fullRecord
        
           if __name__ == "__main__":
    if len(sys.argv) < 2:
        print "Usage:QueryDNS.py www.google.cn"
    else:
        domain = sys.argv[1]
        QueryDNS(domain)
    #######################file domaintobyte.py###########################domaintobyte
#www.google.cn => 03www06google02cn00
#bytetodomain
#03www06google02cn00 => www.google.cn
import sys
import structdef domaintobyte(domain):    #print 'old dimain', domain    domaintobyte = ''    dsplit = domain.split('.')    for cs in dsplit:
        formatstr = 'B%ds' % len(cs)
        newsplit = struct.pack(formatstr, len(cs), cs)
        domaintobyte += newsplit    domaintobyte += '\0'
    #print 'new domain', domaintobyte
    #print repr(domaintobyte) 
    return domaintobytedef bytetodomain(str):
    
    domain = ''
    i = 0
    length = struct.unpack('!B', str[0:1])[0]
    
    while length != 0 :
        i += 1
        domain += str[i:i+length]
        i += length
        length = struct.unpack('!B', str[i:i+1])[0]
        if length != 0 :
            domain += '.'
    
    return domain
